<?php
/**
 * This file defines the {@see \BF\Config\Reader\Xml} class.
 *
 * @author     Kado <ulfikado@gmail.com>
 * @category   Beluga PHP5.3+ Framework
 * @package    BF\Core
 * @since      2015-01-28 14:29
 * @subpackage Config\Reader
 * @version    0.1
 */

namespace BF\Config\Reader
{

   require_once \dirname( __DIR__ ) . '/IReader.interface.php';

   /**
    * @since      v0.1
    */
   class Xml implements \BF\Config\IReader
   {

      # <editor-fold defaultstate="collapsed" desc=" - - -   P R I V A T E   F I E L D S   - - - - - - - - - - - - - - - - - - - - - -">

      /**
       * The path of current open XML config file
       *
       * @var string
       */
      private $file;

      /**
       * The name of the root element, containing the configuration data.
       *
       * @var string
       */
      private $rootElementName;

      /**
       * The name of the XML element, defining a single config value.
       *
       * @var string
       */
      private $configElementName;

      /**
       * @var SimpleXmlElement[] Array
       */
      private $xml;

      /**
       * The position of the current xml file element.
       *
       * @var integer
       */
      private $cpos;

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - >   P U B L I C   C O N S T R U C T O R   - - - - - - - - - - - - - - - - - -">

      /**
       * Inits a new instance.
       *
       * @param string $xmlFile           The path of XML config file
       * @param string $rootElementName   The name of the root element, containing the configuration data. (default='Configs')
       * @param string $configElementName The name of the XML element, defining a single config value. (default='Config')
       */
      public function __construct( $xmlFile, $rootElementName = 'Configs', $configElementName = 'Config' )
      {
         $this->file = $xmlFile;
         $this->rootElementName = empty( $rootElementName )     ? 'Configs' : $rootElementName;
         $this->configElementName = empty( $configElementName ) ? 'Config'  : $configElementName;
         $this->reload();
      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   P U B L I C   M E T H O D S   - - - - - - - - - - - - - - - - - - - - - -">

      /**
       * Reads the next defined config value from source and returns it as a {@see \BF\Config\Value},
       * or it returns (boolean) FALSE if the reading pointer comes to the end of the source.
       *
       * @return \BF\Config\Value Or boolean FALSE
       */
      public function read()
      {
         $result = false;
         while ( true )
         {
            if ( ! $this->canRead() )
            {
               break;
            }
            $xml = $this->xml[ $this->cpos ];
            ++$this->cpos;
            // <Config name="" type="" strict="">VALUE</Config>
            if ( ! isset( $xml[ 'name' ] )
              || ! isset( $xml[ 'type' ] )
              || ! isset( $xml[ 'strict' ] ) )
            {
               continue;
            }
            $valueStr = (string) $xml;
            $value    = null;
            if ( $valueStr != '<NULL>' )
            {
               $value = \BF\TypeTool::StrToType( $valueStr, (string) $xml[ 'type' ] );
            }
            $result = new \BF\Config\Value(
               (string) $xml[ 'name' ],
               $value,
               ( (string) $xml[ 'strict' ] ) == '1',
               (string) $xml[ 'type' ]
            );
            break;
         }
         return $result;
      }

      /**
       * Reads all config values, beginning at current source pointer and returns it.
       *
       * @return \BF\Config\ValueCollection
       */
      public function readAll()
      {
         $result = new \BF\Config\ValueCollection();
         for ( ; $this->cpos < \count( $this->xml ); ++$this->cpos )
         {
            $xml = $this->xml[ $this->cpos ];
            if ( ! isset( $xml[ 'name' ] )
              || ! isset( $xml[ 'type' ] )
              || ! isset( $xml[ 'strict' ] ) )
            {
               continue;
            }
            $name     = (string) $xml[ 'name' ];
            $valueStr = (string) $xml;
            $value    = null;
            if ( $valueStr != '<NULL>' )
            {
               $value = \BF\TypeTool::StrToType( $valueStr, (string) $xml[ 'type' ] );
            }
            $result[ $name ] = new \BF\Config\Value(
               $name,
               $value,
               ( (string) $xml[ 'strict' ] ) == '1',
               (string) $xml[ 'type' ]
            );
         }
         return $result;
      }

      /**
       * Returns the 3 usable Options 'file', 'rootElementName' and 'configElementName'.
       *
       * @return array
       */
      public function getOptions()
      {
         return array(
            'file' => $this->file,
            'rootElementName' => $this->rootElementName,
            'configElementName' => $this->configElementName
         );
      }

      /**
       * Returns, if the current reader is opened.
       *
       * @return boolean
       */
      public function isOpen()
      {
         return ( \count( $this->xml ) > 0 ) && $this->canRead();
      }

      /**
       * Returns, if the current reader is opened and can be used for reading.
       *
       * @return boolean
       */
      public function canRead()
      {
         return $this->cpos < \count($this->xml);
      }

      /**
       * Sets the reader position to the start of the reader.
       */
      public function reset()
      {
          $this->cpos = 0;
      }

      /**
       * Relaods all data from based source.
       */
      public function reload()
      {
         $this->cpos = 0;
         if ( ! \file_exists( $this->file ) )
         {
            $this->xml = array();
            return;
         }
         try
         {
            $xml = \simplexml_load_file( $this->file );
            $this->xml = array();
            if ( ! isset( $xml->{$this->configElementName} ) )
            {
               return;
            }
            foreach ( $xml->{$this->configElementName} as $item )
            {
               $this->xml[] = $item;
            }
         }
         catch ( \Exception $ex )
         {
            $ex = null;
            $this->xml = array();
         }
      }

      # </editor-fold>

   }

}

